﻿using System;
using System.IO;
using Needle.Utils.Parser;
using System.Diagnostics;
using Needle.Utils.Extensions;

namespace Needle.Templates
{

    public class TemplateTokenizer : StringTokenizer<TemplateToken>
    {
        private static readonly char[] _codePunctuators = new char[] { '=', '"' };
        private bool _inCode = false;
        private bool _startTokenRead = false;
        private bool _inTemplateMetadata = false ;

        public TemplateTokenizer(string template)
            : base(template, new char[] { })
        { }

        public TemplateTokenizer(TextReader templateReader)
            : base(templateReader, new char[] { })
        { }

        private TemplateToken CreateToken(TemplateTokenType ttType)
        {
            int l = Buffer.Length;
            return CreateToken(TokenType.Token, ttType, this.Line, this.Column - l, this.Index - l);
        }

        private TemplateToken CreateToken(TokenType type, TemplateTokenType ttType, int line, int col, int index)
        {
            string value = Buffer.Flush();
            return new TemplateToken(value, type, ttType, line, col, index);
        }

        protected override TemplateToken CreateToken(TokenType type, int line, int col, int index)
        {
            TemplateTokenType ttType;
            switch (type)
            {
                case TokenType.None:
                    ttType = TemplateTokenType.None;
                    break;
                case TokenType.EOF:
                    ttType = TemplateTokenType.EOF;
                    break;
                case TokenType.Token:
                case TokenType.NewLine:
                case TokenType.WhiteSpace:
                case TokenType.Punctuator:
                default:
                    ttType = _inCode ? _inTemplateMetadata ? TemplateTokenType.Metadata : TemplateTokenType.Code : TemplateTokenType.Text;
                    break;
            }
            return CreateToken(type, ttType, line, col, index);
        }

        protected override TemplateToken ReadToken()
        {
            char c;
            while (true)
            {
                c = Peek();
                switch (c)
                {
                    case '<':
                        if (_inCode)
                            return ReadCodeToken();
                        else
                            return ReadStartToken();
                    case '\0':
                        return CreateToken(TemplateTokenType.EOF);
                    default:
                        if (_inCode)
                            return ReadCodeToken();
                        else
                            return ReadTextToken();
                }
            }
        }

        private TemplateToken ReadStartToken()
        {
            this.Read();
            char c = Peek();
            if (c == '#')
            {
                _inCode = true;
                _startTokenRead = true;
                Read();
                return CreateToken(TemplateTokenType.EndText);
            }
            else
            {
                return ReadTextToken();
            }
        }

        private TemplateToken ReadTextToken()
        {
            char c;
            while (true)
            {
                c = Peek();
                switch (c)
                {
                    case '<':
                        if (Buffer.Length > 0)
                            return CreateToken(TemplateTokenType.Text);
                        else
                            return ReadStartToken();
                    default:
                        if (c=='\0' || IsNewLine(c) || IsWhiteSpace(c))
                            return CreateToken(TemplateTokenType.Text);
                        else
                            Read();
                        break;
                }
            }
        }

        private TemplateToken CreateCodeToken()
        {
            Debug.Assert(_inCode, "not in code section");
            return CreateToken(_inTemplateMetadata ? TemplateTokenType.Metadata : TemplateTokenType.Code);
        }
        private TemplateToken ReadCodeToken()
        {

            char c;
            bool startTokenRead = _startTokenRead;
            _startTokenRead = false;
            while (true)
            {
                c = Peek();
                switch (c)
                {
                    case '#':
                        if (Buffer.Length > 0)
                            return CreateCodeToken();
                        return ReadEndToken();
                    case '"':
                        if (Buffer.Length > 0)
                            return CreateCodeToken();
                        return ReadStringToken();
                    case '=':
                        if (Buffer.Length > 0)
                            return CreateCodeToken();
                        Read();
                        return CreateCodeToken();
                    case '\0':
                        throw new ParseException(Line, Column, String.Format("Unterminated {0} section", _inTemplateMetadata ? "metadata" : "code"));
                    case 'T':
                        if (startTokenRead)
                        {
                            Read();
                            _inTemplateMetadata = true;
                            return CreateCodeToken();
                        }
                        else
                            Read();
                        break;
                    default:
                        if (IsNewLine(c))
                        {
                            if (Buffer.Length > 0)
                                return CreateCodeToken();
                            return this.ReadNewLineToken();
                        }
                        if (IsWhiteSpace(c))
                        {
                            if (Buffer.Length > 0)
                                return CreateCodeToken();
                            return this.ReadWhiteSpaceToken();
                        }  
                        Read();
                        break;
                }
            }
        }

        private TemplateToken ReadStringToken()
        {
            char c = Peek();
            if (c == '"')
                Read();
            else
                throw new ParseException(Line, Column, "Expected string delimiter '\"' ");

            while (true)
            {
                c = Peek();
                switch (c)
                {
                    case '"':
                        Read();
                        return CreateCodeToken();
                    case '\0':
                        throw new ParseException(CreateCodeToken(), "unterminated string");
                    default:
                        if (c == EscapeChar)
                            Buffer.Append((char)ParseEscapeSequence());
                        else
                            Read();
                        break;
                }
            }
        }

        private TemplateToken ReadEndToken()
        {
            if (Read() == '#' && '>' == Read())
            {
                _inCode = false;
                _startTokenRead = false;
                _inTemplateMetadata = false;
                return CreateToken(TemplateTokenType.StartText);
            }
            return ReadCodeToken();
        }
    }

}